package com.hwtx.form.persistence;

import cn.hutool.extra.spring.SpringUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.Maps;
import com.hwtx.form.domain.def.FormDef;
import com.hwtx.form.domain.def.FormProperties;
import com.hwtx.form.domain.handle.FormI18nHandle;
import com.hwtx.form.domain.repo.FormConfigRepo;
import com.hwtx.form.domain.repo.FormRepo;
import com.hwtx.form.dto.FormDefDto;
import com.hwtx.form.dto.FormQuery;
import com.hwtx.form.persistence.entity.FormDefDetail;
import com.hwtx.form.persistence.entity.FormDefEntity;
import com.hwtx.form.persistence.mapper.FormChangeLogMapper;
import com.hwtx.form.persistence.mapper.FormDefMapper;
import com.hwtx.form.util.FileMonitor;
import com.hwtx.form.util.FormUtil;
import com.hwtx.form.util.JacksonUtil;
import io.geekidea.boot.auth.cache.LoginCache;
import io.geekidea.boot.config.DataPermissionFilter;
import io.geekidea.boot.config.FormDataPermissionFilter;
import io.geekidea.boot.config.properties.BootProperties;
import io.geekidea.boot.framework.exception.BusinessException;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.monitor.FileAlterationListenerAdaptor;
import org.apache.commons.io.monitor.FileAlterationObserver;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;

import static com.hwtx.form.domain.FormConstants.FORM_MANAGE_HANDLE_NAME;
import static com.hwtx.form.domain.FormConstants.getUnChangeFormIds;
import static com.hwtx.form.util.FormUtil.*;

/**
 * 表单定义 服务实现类
 *
 * @author hwtx
 * @since 2023-12-10
 */
@Slf4j
@Repository
public class FormRepoImpl extends ServiceImpl<FormDefMapper, FormDefEntity> implements FormRepo {

    private static final Cache<Long, FormDef> formCache = CacheBuilder.newBuilder().maximumSize(50000).recordStats().build();
    private final Map<String, Long> formNameMapping = new HashMap<>();
    private final Map<Long, Long> formIdAppendTo = Maps.newHashMap();
    @Resource
    private FormDefMapper formDefMapper;
    @Resource
    private FormChangeLogMapper formChangeLogMapper;
    @Resource
    private FormConfigRepo formConfigRepo;
    @Resource
    private BootProperties bootProperties;

    @PostConstruct
    public void init() throws Exception {
        if (isLocal()) {
            File dir = getPageDir();
            if (BooleanUtils.isTrue(bootProperties.getForm().getMonitorFileChange())) {
                FileAlterationObserver observer = getFileAlterationObserver(dir);
                FileMonitor fileMonitor = new FileMonitor();
                fileMonitor.start(1, observer);
            }
            Collection<File> files = FileUtils.listFiles(dir, new String[]{"json"}, true);
            for (File file : files) {
                if (file.getParentFile().getName().equals("tmp")) {
                    continue;
                }
                log.info("初始化本地表单，file = {}", file.getName());
                buildFileFormDef(file);
            }
            getUnChangeFormIds().forEach(id -> {
                String name = getFormNameById(id);
                if (name == null) {
                    throw new RuntimeException("表单id = " + id + "对应的表单不存在");
                }
            });
        }
    }

    @Override
    public Long getAppendToFormId(Long formId) {
        if (formId == null) {
            return null;
        }
        return formIdAppendTo.get(formId);
    }

    @NotNull
    private FileAlterationObserver getFileAlterationObserver(File dir) {
        FileAlterationObserver observer = new FileAlterationObserver(dir);
        observer.addListener(new FileAlterationListenerAdaptor() {
            @Override
            public void onFileCreate(File file) {
                log.info("添加文件，file = {}", file.getName());
                buildFileFormDef(file);
            }

            @Override
            public void onFileChange(File file) {
                log.info("文件发生变更，file = {}", file.getName());
                try {
                    Long formId = FormUtil.getFormId(file);
                    if (formId != null) {
                        buildFileFormDef(file);
                    } else {
                        log.error("文件发生变更，formId为空，file = {}", file.getName());
                    }
                } catch (Exception e) {
                    log.error("文件发生变更，发生异常，file = {}", file.getName(), e);
                }
            }

            @Override
            public void onFileDelete(File file) {
                log.info("文件删除，file = {}", file.getName());
                removeMapping(file);
            }
        });
        return observer;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public long addFormDef(FormDefDto dto) throws IOException {
        if (isLocal()) {
            String fileName = dto.getName();
            if (!dto.getName().startsWith(dto.getType())) {
                fileName = dto.getType() + "-" + dto.getName();
            }
            validateForm(dto);
            FormProperties formProperties = new FormProperties();
            formProperties.setName(fileName);
            if (dto.getFormId() == null) {
                formProperties.setFormId(genFormId(null));
            }
            File file = getLocalFile(formProperties);
            if (file.exists()) {
                log.error("表单定义已存在,file = {}", file.getName());
                throw new BusinessException("表单定义已存在");
            }
            FileUtils.writeStringToFile(file, dto.getContent(), StandardCharsets.UTF_8);
            buildFileFormDef(file);
            return formProperties.getFormId();
        }
        FormDefEntity formDefEntity = new FormDefEntity();
        BeanUtils.copyProperties(dto, formDefEntity);
        save(formDefEntity);
        return formDefEntity.getId();
    }

    @Override
    public Long genFormId(Long formId) {
        if (isLocal()) {
            Long maxId = Collections.max(formCache.asMap().keySet());
            if (formId == null) {
                return maxId + 1;
            } else {
                if (formCache.getIfPresent(formId) != null) {
                    log.error("表单id已存在,id = {}", formId);
                }
            }
            return formId;
        }
        QueryWrapper<FormDefEntity> wrapper = new QueryWrapper<>();
        wrapper.select("MAX(id) as maxId");
        Map<String, Object> map = getMap(wrapper);
        return map != null ? (Long) map.get("maxId") : null;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean updateFormDef(FormDefDto dto) throws Exception {
        if (isLocal()) {
            if (dto.getName().startsWith(dto.getType())) {
                FormProperties formProperties = formCache.getIfPresent(dto.getId());
                if (formProperties == null) {
                    log.error("表单定义不存在,无法更新表单定义,id = {}", dto.getId());
                    return false;
                }
                File file = getLocalFile(formProperties);
                if (file.exists()) {
                    FileUtils.writeStringToFile(file, dto.getContent(), StandardCharsets.UTF_8);
                    buildFileFormDef(file);
                    return true;
                } else {
                    throw new BusinessException("表单定义不存在,formId = " + dto.getId());
                }
            } else {
                throw new BusinessException("表单名称必须以类型开头");
            }
        }
        Long id = dto.getId();
        FormDefEntity formDefEntity = getById(id);
        if (formDefEntity == null) {
            throw new BusinessException("表单定义不存在");
        }
        BeanUtils.copyProperties(dto, formDefEntity);
        formDefEntity.setUpdateTime(new Date());
        formDefEntity.setUpdateId(LoginCache.get().getUsername());
        boolean ret = updateById(formDefEntity);
        if (ret) {
            FormDef formDef = buildFormDef(dto.getId());
            if (formDef != null) {
                handleCache(formDef.getFormId(), formDef.getName(), formDef);
            }
        }
        return ret;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean deleteFormDef(Long id) {
        if (isLocal()) {
            FormProperties formProperties = formCache.getIfPresent(id);
            if (formProperties == null) {
                log.error("表单定义不存在,id = {}", id);
                throw new BusinessException("表单定义不存在");
            }
            formCache.invalidate(id);
            File file = getLocalFile(formProperties);
            if (file.exists()) {
                removeMapping(formProperties);
                return file.delete();
            }
            return true;
        } else {
            return removeById(id);
        }
    }

    @Override
    public boolean containsFormDef(Long formId) {
        return formCache.asMap().containsKey(formId);
    }

    @Override
    public boolean containsFormDef(String formName) {
        return formNameMapping.containsKey(formName);
    }

    @Override
    public String getFormRawContentById(Long formId) throws Exception {
        String content = loadContentByLocal(formId);
        if (content != null) {
            return content;
        }
        FormDefEntity entity = formDefMapper.getFormDefById(formId);
        if (entity != null) {
            return entity.getContent();
        }
        return null;
    }

    @Override
    public String getFormRawContentByName(String formName) throws Exception {
        return getFormRawContentById(getFormIdByName(formName));
    }

    @Override
    public String getFormNameById(Long formId) {
        FormProperties formProperties = getFormProperties(formId);
        return formProperties.getName();
    }

    @Override
    public Long getFormIdByName(String formName) {
        if (StringUtils.isBlank(formName)) {
            return null;
        }
        FormDef formProperties = formCache.getIfPresent(formNameMapping.get(formName));
        if (formProperties == null) {
            throw new BusinessException("表单不存在,formName = " + formName);
        }
        return formProperties.getFormId();
    }

    @Override
    public FormDef getFormDefByName(String formName) {
        if (StringUtils.isBlank(formName)) {
            return null;
        }
        Long formId = formNameMapping.get(formName);
        if (formId == null) {
            return null;
        }
        return formCache.getIfPresent(formId);
    }

    @Override
    public FormDef getFormDef(Long formId) {
        try {
            return formCache.get(formId, () -> {
                if (isLocal()) {
                    log.error("表单定义不存在,formId = {}", formId);
                    return null;
                }
                FormDef formDef = buildFormDef(formId);
                if (formDef != null) {
                    handleMapping(formId, formDef.getName());
                }
                return formDef;
            });
        } catch (ExecutionException e) {
            log.error("获取表单定义失败,formId = {}", formId, e);
            throw new RuntimeException(e);
        }
    }

    private String loadContentByLocal(Long formId) throws IOException {
        return loadContentByLocal(getFormProperties(formId));
    }

    private String loadContentByLocal(FormProperties formProperties) throws IOException {
        if (isLocal()) {
            File file = getLocalFile(formProperties);
            if (file.exists()) {
                return FileUtils.readFileToString(file, StandardCharsets.UTF_8);
            }
        }
        return null;
    }

    @Override
    public void saveFormChangeLog(FormChangeLog formChangeLog) {
        formChangeLog.setLastModifyTime(new Date());
        formChangeLog.setCreateTime(new Date());
        formChangeLogMapper.insert(formChangeLog);
    }

    @Override
    public String getFormTypeById(Long formId) {
        return getFormProperties(formId).getType();
    }

    @Override
    public List<String> listFormType() {
        if (isLocal()) {
            return formCache.asMap().values().stream().map(FormProperties::getType).distinct().collect(Collectors.toList());
        }
        return formDefMapper.listFormType();
    }

    @Override
    public List<FormProperties> listFormInfo(FormQuery formQuery) {
        if (isLocal()) {
            FormDataPermissionFilter formDataPermissionFilter = (FormDataPermissionFilter) SpringUtil.getApplicationContext().getBeansOfType(DataPermissionFilter.class).values().stream().filter(f -> Objects.equals(f.getType(), FORM_MANAGE_HANDLE_NAME)).findFirst().orElse(null);
            return formCache.asMap().values().stream().filter(formProperties -> {
                boolean matchType = true;
                if (CollectionUtils.isNotEmpty(formQuery.getTypes())) {
                    matchType = formQuery.getTypes().contains(formProperties.getType());
                }
                if (formDataPermissionFilter != null) {
                    matchType = matchType && formDataPermissionFilter.filter(formProperties);
                }
                if (StringUtils.isNotBlank(formQuery.getFormId())) {
                    matchType = matchType && formProperties.getFormId().toString().contains(formQuery.getFormId());
                }
                if (formQuery.getConfig() != null) {
                    matchType = matchType && Objects.equals(formProperties.isConfig(), formQuery.getConfig());
                }
                if (StringUtils.isNotBlank(formQuery.getStartWithName())) {
                    matchType = matchType && formProperties.getName().startsWith(formQuery.getStartWithName());
                }
                if (StringUtils.isNotBlank(formQuery.getFormName())) {
                    return matchType && formProperties.getName().contains(formQuery.getFormName());
                }
                return matchType;
            }).peek(formProperties -> formProperties.setTitle(FormI18nHandle.getValue(formProperties.getMeta().getTitle()))).sorted(Comparator.comparing(FormProperties::getUpdateTime).reversed()).collect(Collectors.toList());
        }
        return formDefMapper.list(formQuery);
    }

    private void buildFileFormDef(File file) {
        FormProperties formProperties;
        try {
            formProperties = getLocalFormInfo(file);
            String content = loadContentByLocal(formProperties);
            if (content != null) {
                handleCache(formProperties.getFormId(), formProperties.getName(), FormDef.of(content, formProperties));
            } else {
                throw new BusinessException("构建文件表单信息异常,file = " + file.getName());
            }
        } catch (IOException e) {
            throw new BusinessException(e);
        }
    }

    private FormDef buildFormDef(Long formId) {
        FormDefEntity entity = formDefMapper.getFormDefById(formId);
        if (entity != null) {
            FormDef formDef = FormDef.of(entity.getName(), entity.getContent(), entity.getId(), entity.getType(), entity.getClassify());
            formDef.setFormId(entity.getId());
            formDef.setSystem(entity.getSystem());
            formDef.setType(entity.getType());
            formDef.setClassify(entity.getClassify());
            FormDefDetail formDetail = formConfigRepo.getFormDetail(formId);
            formDef.setConfig(formDetail != null);
            return formDef;
        }
        log.error("表单不存在，formId = {}", formId);
        return null;
    }

    private void handleCache(Long formId, String formName, FormDef formDef) {
        formCache.put(formId, formDef);
        String appendToId = formDef.getMeta().getAppendTo();
        if (StringUtils.isNotEmpty(appendToId)) {
            formIdAppendTo.put(formId, Long.parseLong(appendToId));
        }
        handleMapping(formId, formName);
    }

    private void handleMapping(Long formId, String formName) {
        if (MapUtils.isNotEmpty(bootProperties.getForm().getAlias())) {
            String alias = bootProperties.getForm().getAlias().get(formName);
            if (StringUtils.isNotBlank(alias)) {
                formNameMapping.put(alias, formId);
            }
        }
        formNameMapping.put(formName, formId);
    }

    private synchronized void removeMapping(File file) {
        FormProperties formProperties;
        try {
            formProperties = getLocalFormInfo(file);
        } catch (IOException e) {
            throw new BusinessException(e);
        }
        Long formId = formNameMapping.get(formProperties.getName());
        if (Objects.equals(formProperties.getFormId(), formId)) {
            removeMapping(formProperties);
        }
        formCache.invalidate(formProperties.getFormId());
    }

    private synchronized void removeMapping(FormProperties formProperties) {
        formNameMapping.remove(formProperties.getName());
    }

    private FormProperties getFormProperties(Long formId) {
        try {
            FormProperties formProperties = getFormDef(formId);
            if (formProperties == null) {
                throw new BusinessException("表单不存在,formId = " + formId);
            }
            return formProperties;
        } catch (Exception e) {
            log.error("获取表单定义异常", e);
            throw new BusinessException("获取表单信息异常,formId = " + formId);
        }
    }

    private File getLocalFile(FormProperties formProperties) {
        if (formProperties.getSystem() != null) {
            return new File(formProperties.getSystem() ? getSysPageDir() : getAppPageDir(), formProperties.getName() + "_" + formProperties.getFormId() + ".json");
        }
        return new File(getAppPageDir(), formProperties.getName() + "_" + formProperties.getFormId() + ".json");
    }

    private boolean isLocal() {
        return bootProperties.getForm().isLoadFormByLocal();
    }

    private void validateForm(FormDefDto dto) {
        if (dto.getName().contains("_")) {
            throw new BusinessException("表单名称不能包含下划线");
        }
        String content = dto.getContent();
        if (StringUtils.isBlank(content)) {
            throw new BusinessException("表单内容不能为空");
        }
        Map<String, Object> map = JacksonUtil.json2Map(content);
        if (!map.containsKey(FormProperties.FORM_META)) {
            FormProperties.Meta meta = new FormProperties.Meta();
            meta.title("#i18n{form.manage}").createBy(LoginCache.get().getUsername());
            map.put(FormProperties.FORM_META, meta);
            dto.setContent(JacksonUtil.object2json(map));
        }
    }
}
